
#include "core/olisp_core.h"
#include "core/olisp_mem.h"


typedef unsigned int hash_t;

static hash_t calc_hash( const unsigned char * str, size_t len )
{
	size_t n = len;
	hash_t h = 0;
	hash_t g;

	while( n-- )
	{
		h = (h << 4) + *str++;
		if ((g = (h & 0xf0000000)) != 0)
			h ^= g >> 23;
		h &= ~g;
	}

	return h;
}


typedef struct
{
    hash_t          hash;
    const char *    str;
} hash_pair_t;

static struct
{
    hash_pair_t *   pairs;
    size_t          allocated;
    size_t          length;
} hash_collection = { 0 };

#define HASH_VEC_GROW_SIZE      1024

static const char * find_string_in_cache( hash_t hash )
{
    size_t      i;

    // find whether the string is in cache now
    for( i = 0; i < hash_collection.length; ++ i )
    {
        if( hash_collection.pairs[i].hash == hash )
        {
            return hash_collection.pairs[i].str;
        }
    }

    return 0;
}

static int add_hashed_string_to_cache( const char * added_str, hash_t hash )
{
    hash_pair_t   * pair;
    size_t          new_len = hash_collection.length + 1;
    if( new_len > hash_collection.allocated )
    {
        size_t new_alloc = hash_collection.allocated + HASH_VEC_GROW_SIZE;
        hash_pair_t * new_pairs_buf = lisp_realloc( hash_collection.pairs,
                                    new_len * sizeof(hash_pair_t) );
        if( !new_pairs_buf )
            return 0;

        hash_collection.pairs = new_pairs_buf;
        hash_collection.allocated = new_alloc;
    }

    // choose last one
    pair = hash_collection.pairs + hash_collection.length;
    pair->hash = hash;
    pair->str = added_str;

    hash_collection.length = new_len;

    return 1;
}





static struct
{
    char *          buf;
    size_t          allocated;
    size_t          length;
    int             ref_count;
} string_cache = { 0 };

#define STRING_CACHE_SIZE       65536

static hash_t add_string_to_cache( const char * str )
{
    size_t          str_len = strlen( str );
    hash_t          hash = calc_hash( (const unsigned char *)str, str_len );
    const char *    alloc_str;
    size_t          new_length;

    // take into an account string's length
    ++ str_len;

    // check whether it was added
    alloc_str = find_string_in_cache( hash );
    if( 0 != alloc_str )
    {
        return hash;
    }

    // add string to the buffer
    new_length = string_cache.length + str_len;
    while( new_length > string_cache.allocated )
    {
        // do realloc
        size_t new_alloc = string_cache.allocated + STRING_CACHE_SIZE;
        char * new_buf = lisp_realloc( string_cache.buf, new_alloc * sizeof(char) );
        if( new_buf == 0 )
            return 0;

        string_cache.buf = new_buf;
        string_cache.allocated = new_alloc;
    }

    // update buffer
    memcpy( string_cache.buf + string_cache.length, str, str_len );
    string_cache.length = new_length;

    // add to hash collection
    if( !add_hashed_string_to_cache(str, hash) )
        return 0;

    // update references counter
    ++ string_cache.ref_count;

    return hash;
}

static void release_hashed_string()
{
    if( !-- string_cache.ref_count )
    {
        // do clean-up
        if( string_cache.buf )
        {
            lisp_free( string_cache.buf );
            string_cache.buf = 0;
        }
        
        string_cache.allocated = 0;
        string_cache.length = 0;
    }
}



/*
 * string structure
 */
typedef struct id_impl_t
{
    id_lisp_elem_vtbl_t * vtbl;
    
    int ref_count;

    const char * str;
} id_impl_t;

static int id_add_ref( id_lisp_elem_t * self )
{
    id_impl_t * s = (id_impl_t *)self;
    return ++s->ref_count;
}

static int id_release( id_lisp_elem_t * self )
{
    id_impl_t * s = (id_impl_t *)self;
    int res = --s->ref_count;
    
    if( res > 0 )
        return res;

    //lisp_free( s->str );
    lisp_free( s );

    release_hashed_string();
    return 0;
}

static const char * id_get_str( id_lisp_elem_t * self )
{
    id_impl_t * s = (id_impl_t *)self;
    return s->str;
}

static id_lisp_elem_vtbl_t id_lisp_elem_vtbl =
{
    LISP_ELEM_ID,
    &id_add_ref,
    &id_release,
    0,
    &id_get_str
};

/*
 * creates hashed identifier
 */
id_lisp_elem_t * create_hashed_id( const char * id )
{
    id_impl_t * s = lisp_malloc( sizeof(id_impl_t) );
    if( s )
    {
        hash_t h = add_string_to_cache( id );
        s->str = find_string_in_cache( h );
        //s->str = lisp_strdup( id );
        if( s->str )
        {
            s->ref_count = 1;
            s->vtbl = &id_lisp_elem_vtbl;

            return (id_lisp_elem_t *)s;
        }

        lisp_free( s );
    }

    return 0;
}

